package yu.ac.bg.etf.kdp.radnastanica;

import java.io.*;
import java.net.*;
import java.util.*;
import yu.ac.bg.etf.kdp.klase.*;
import rs.ac.bg.etf.kdp.math.*;

public class WorkstationThread extends Thread {

	private static int cnt;
	private final int id;

	private Socket socket;
	private Workstation workstation;

	public WorkstationThread(Workstation workstation, Socket socket) {
		id = cnt++;
		this.socket = socket;
		this.workstation = workstation;
	}

	public void run() {
		try {
			// inicijalizujemo tokove podataka
			ObjectOutputStream outo = new ObjectOutputStream(socket.getOutputStream());
			outo.flush();
			ObjectInputStream ino = new ObjectInputStream(socket.getInputStream());
			// obradjujemo zahtev po protokolu komunikacije
			Msg msg = (Msg) ino.readObject();
			String reqId = (String) msg.getBody();
			if (reqId.equals("serverRequest")) {
				msg = (Msg) ino.readObject();
				reqId = (String) msg.getBody();
				if (reqId.equals("stationAvailable")) {
					System.out.println("CHECK");
					outo.writeObject(msg);
					outo.flush();
				} else if (reqId.equals("jobRequest")) {
					// primamo posao sa servera
					Job job = (Job) ino.readObject();
					// primamo fajlove potrebne za obradu posla
					FileTransfer.recieveFile(ino, "a" + id + ".txt");
					if (job.getCommand().equals("solve") || job.getCommand().equals("calculate")) {
						FileTransfer.recieveFile(ino, "b" + id + ".txt");
					}
					// pozivamo metodu za obradu posla koja ce i da vrati
					// fajl sa rezultatima na server 
					handleJob(job);
					outo.close();
					ino.close();
					socket.close();
				} else if (reqId.equals("wstaConnect")) {
					NodeList nodeList = (NodeList)ino.readObject();
					// i informacije o radnim stanicama sa kojima smo vec konektovani
					List<String> connHostList = workstation.connector.getConnecttedHostList();
					// treba da se preko interfejsa Connector povezemo sa stanicama sa kojima
					// nismo vec bili povezani od ranije
					for (int i = 0;i < nodeList.size();i++) {
						Node node = (Node)nodeList.get(i);
						if (connHostList.indexOf(node.getWstaServerHost()) != -1) {
							workstation.connector.addHost(node.getWstaServerHost() , node.getWstaServerPort());
						}
					}
					workstation.connector.setPort(workstation.workstationsListenerPort);
					workstation.connector.connect();
				} else {
					System.out.println("Bad server request to workstation..");
					System.exit(1);
				}
			} else {
				System.out.println("Bad server request..");
				System.exit(1);
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			System.out.println("Connection broken..");
			System.exit(1);
		} catch (ClassNotFoundException e1) {
			System.out.println("Class not found..");
			System.exit(1);
		}
		
	}
	
	public void handleJob (Job job) {
		// ova metoda treba da obradi ovaj posao
		// i da rezultate (results.txt) posalje serveru
		double[][] resultMatrix;
		double[] resultVector;
		double resultNumber;
		// ucitavamo matricu A
		double[][] valuesA = null;
		String thisLine = null;
		BufferedReader myInput = null;
		try {
			BufferedInputStream s = new BufferedInputStream(
					new java.io.FileInputStream("a" + id + ".txt"));
			myInput = new BufferedReader(new java.io.InputStreamReader(s));
		} catch (Exception e) {
			// TODO: handle exception
		}
		// trazimo velicinu matrice
		int matrixSize = 0;
		try {
			thisLine = myInput.readLine();
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		java.util.StringTokenizer st = new java.util.StringTokenizer(thisLine,
				" ");
		while (st.hasMoreElements()) {
			matrixSize++;
			st.nextToken();
		}
		// alociramo niz i matricu za rezultat
		resultMatrix = new double[matrixSize][matrixSize];
		resultVector = new double[matrixSize];
		// sada alociramo niz i punimo ga iz fajla
		int j = 0;
		st = new java.util.StringTokenizer(thisLine, " ");
		valuesA = new double[matrixSize][matrixSize];
		for (int i = 0; i < matrixSize; i++) {
			try {
				valuesA[0][i] = Float.valueOf(st.nextToken()).floatValue();
			} catch (Exception e) {
				// TODO: handle exception
				System.out.println("Bad element of matrix..");
				System.out.println("Element converted to float 0.0 ..");
			}
		}
		for (int r = 1; r < matrixSize; r++) {
			try {
				thisLine = myInput.readLine();
				if (thisLine == null) {
					System.out.println("Bad input matrix file (a.txt)..");
					System.exit(1);
				}
				st = new StringTokenizer(thisLine, " ");
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			for (int c = 0; c < matrixSize; c++)
				valuesA[r][c] = Float.valueOf(st.nextToken()).floatValue();
		}
		// treba u zavisnosti od komande koja se primanjuje
		// ucitati i vektor iz b.txt
		double[] valuesB = null;
		if (job.getCommand().equals("solve")
				|| job.getCommand().equals("calculate")) {
			// ucitavamo vektor B
			thisLine = null;
			myInput = null;
			try {
				BufferedInputStream s = new BufferedInputStream(
						new java.io.FileInputStream("b" + id + ".txt"));
				myInput = new BufferedReader(new java.io.InputStreamReader(s));
			} catch (Exception e) {
				// TODO: handle exception
			}
			// sada alociramo niz i punimo ga iz fajla
			LinkedList list = new LinkedList();
			try {
				while ((thisLine = myInput.readLine()) != null) {
					st = new StringTokenizer(thisLine, "\n");
					list.add(Float.valueOf(st.nextToken()).floatValue());
				}
				int listSize = list.size();
				valuesB = new double[listSize];
				for (int i = 0; i < listSize; i++) {
					valuesB[i] = ((Float) list.remove()).floatValue();
				}
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		workstation.connector = Creator.createConnector();
		// uzimamo informacije o aktivnim radnim stanicama sa servera
		NodeList nodeList = workstation.getOtherWorkstations();
		// i informacije o radnim stanicama sa kojima smo vec konektovani
		List<String> connHostList = workstation.connector.getConnecttedHostList();
		// treba da se preko interfejsa Connector povezemo sa stanicama sa kojima
		// nismo vec bili povezani od ranije
		for (int i = 0;i < nodeList.size();i++) {
			Node node = (Node)nodeList.get(i);
			if (connHostList.indexOf(node.getWstaServerHost()) != -1) {
				workstation.connector.addHost(node.getWstaServerHost() , node.getWstaServerPort());
			}
		}
		// sad treba da obavestimo i ostale radne stanice iz ove liste da
		// i one trebaju da se konektuju i to radimo posredstvom servera
		try {
			// kacimo se na server
			Socket sock = new Socket(workstation.serverHost, workstation.serverPort);
			// uzimamo tokove podataka
			ObjectOutputStream outo = new ObjectOutputStream(sock.getOutputStream());
			ObjectInputStream ino = new ObjectInputStream(sock.getInputStream());
			// saljemo po protokolu
			outo.writeObject(new MsgTxt("wstaRequest"));
			outo.flush();
			outo.writeObject(new MsgTxt("wstaConnect"));
			outo.flush();
			outo.writeObject(nodeList);
			outo.flush();
			outo.writeObject(new MsgTxt(workstation.localHost));
			outo.flush();
			outo.writeObject(new MsgTxt(""+workstation.workstationsListenerPort));
			outo.flush();
			outo.close();
			ino.close();
			sock.close();
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			System.out.println("Connection broken..");
			System.exit(1);
		}
		// uspostavljamo konekciju
		workstation.connector.setPort(workstation.workstationsListenerPort);
		workstation.connector.connect();
		// postavljamo radne stanice koje ucestvuju u poslu
		job.setInvolvedWorkstations(nodeList);
		try {
			// ucitali smo fajlove u matrice i sada treba da
			// pozovemo odgovarajucu funkciju klase Equations
			// i da upisemo rezultat u izlazni fajl
			if ("solve".equals(job.getCommand())) {
				resultVector = workstation.equations.solve(valuesA, valuesB);
				writeVectorToFile(resultVector, job.getResults());
				job.setStatus(Job.JOB_DONE);
			} else if ("calculate".equals(job.getCommand())) {
				resultVector = workstation.equations.calculate(valuesA, valuesB);
				writeVectorToFile(resultVector, job.getResults());
				job.setStatus(Job.JOB_DONE);
			} else if ("getLUDecomposition".equals(job.getCommand())) {
				resultMatrix = workstation.equations.getLUDecomposition(valuesA);
				writeMatrixToFile(resultMatrix, job.getResults());
				job.setStatus(Job.JOB_DONE);
			} else if ("getEigenvalueDecomposition".equals(job.getCommand())) {
				resultVector = workstation.equations.getEigenvalueDecomposition(valuesA);
				writeVectorToFile(resultVector, job.getResults());
				job.setStatus(Job.JOB_DONE);
			} else if ("getInverseMatrix".equals(job.getCommand())) {
				resultMatrix = workstation.equations.getInverseMatrix(valuesA);
				writeMatrixToFile(resultMatrix, job.getResults());
				job.setStatus(Job.JOB_DONE);
			} else if ("getMatrixDeterminant".equals(job.getCommand())) {
				resultNumber = workstation.equations.getMatrixDeterminant(valuesA);
				writeNumberToFile(resultNumber, job.getResults());
				job.setStatus(Job.JOB_DONE);
			} else {
				System.out.println("Wrong command..");
				System.exit(1);
			}
		} catch (Exception e) {
			// TODO: handle exception
			// desila se greska pri obradi zahteva
			// i posao nije uspeo da se odradi
			job.setStatus(Job.JOB_FAILED);
			try {
				// kacimo se na server
				Socket sock = new Socket(workstation.serverHost, workstation.serverPort);
				// uzimamo tokove podataka
				ObjectOutputStream outo = new ObjectOutputStream(sock.getOutputStream());
				outo.flush();
				ObjectInputStream ino = new ObjectInputStream(sock.getInputStream());
				// saljemo po protokolu
				outo.writeObject(new MsgTxt("wstaRequest"));
				outo.flush();
				outo.writeObject(new MsgTxt("sendingResults"));
				outo.flush();
				outo.writeObject(job);
				outo.flush();
				Msg msg = (Msg)ino.readObject();
				String str = (String) msg.getBody();
				outo.close();
				ino.close();
				sock.close();
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				System.out.println("Connection broken..");
				System.exit(1);
			} catch (ClassNotFoundException e2) {
				System.out.println("Class not found..");
				System.exit(1);
			}
		}
		if (job.getStatus() == Job.JOB_DONE) {
			// sad treba izlazni fajl da posaljemo na server
			// uz postovanje komunikacionog protokola
			try {
				// kacimo se na server
				Socket sock = new Socket(workstation.serverHost,
						workstation.serverPort);
				// uzimamo tokove podataka
				ObjectOutputStream outo = new ObjectOutputStream(sock
						.getOutputStream());
				outo.flush();
				ObjectInputStream ino = new ObjectInputStream(sock
						.getInputStream());
				// saljemo po protokolu
				outo.writeObject(new MsgTxt("wstaRequest"));
				outo.flush();
				outo.writeObject(new MsgTxt("sendingResults"));
				outo.flush();
				outo.writeObject(job);
				outo.flush();
				Msg msg = (Msg)ino.readObject();
				String str = (String) msg.getBody();
				FileTransfer.sendFile(outo, ""+ id +job.getResults());
				outo.close();
				ino.close();
				sock.close();
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				System.out.println("Connection broken..");
				System.exit(1);
			} catch (ClassNotFoundException e2) {
				System.out.println("Class not found..");
				System.exit(1);
			}
		}
	}

	private void writeNumberToFile(double resultNumber, String results) {
		try {
			Writer output = new BufferedWriter(
					new FileWriter(""+ id + results));
			String number = Double.toString(resultNumber);
			output.write(number);
			output.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	private void writeMatrixToFile(double[][] resultMatrix, String results) {
		// TODO Auto-generated method stub
		if (resultMatrix.length != resultMatrix[0].length) {
			System.out.println("Bad input matrix dimensions..");
			System.exit(1);
		}
		try {
			Writer output = new BufferedWriter(
					new FileWriter(""+ id + results));
			for (int i = 0; i < resultMatrix.length; i++) {
				for (int j = 0; j < resultMatrix.length; j++) {
					String number = Double.toString(resultMatrix[i][j]);
					output.write(number + " ");
				}
				output.write("\n");
			}
			output.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	private void writeVectorToFile(double[] resultVector, String results) {
		// TODO Auto-generated method stub
		try {
			Writer output = new BufferedWriter(
					new FileWriter(""+ id + results));
			for (int i = 0; i < resultVector.length; i++) {
				String number = Double.toString(resultVector[i]);
				output.write(number + "\n");
			}
			output.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}
